#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import types
import logging
from traceback import format_exc
from os.path import abspath, dirname, join, isfile, isdir
from werkzeug import ClosingIterator
from werkzeug.routing import Map, Rule, Submount, Subdomain
from werkzeug.exceptions import HTTPException, InternalServerError
from werkzeug.wrappers import BaseResponse
from werkzeug.contrib.sessions import SessionStore
from .wrappers import AccostRequest
from .handlers import Handler
from .globals import _request_ctx_stack, request
__all__ = ['WSGIApplication']
logger = logging.getLogger("accost")


def get_modules(prefix, parent):
    if not isfile(join(parent, '__init__.py')):
        return
    for pathname in os.listdir(parent):
        child = join(parent, pathname)
        if pathname.startswith('.'):
            continue
        elif pathname == '__init__.py':
            yield '.'.join(prefix)
        elif isdir(child):
            for i in get_modules(prefix+[pathname], child):
                yield i
        elif isfile(child) and pathname.endswith('.py'):
            yield '.'.join(prefix+[pathname[:-3]])


class RequestContext(object):
    def __init__(self, environ):
        self.request = AccostRequest(environ)
        self.flashes = None

    def push(self):
        _request_ctx_stack.push(self)

    def pop(self):
        _request_ctx_stack.pop()

    def __enter__(self):
        self.push()
        return self

    def __exit__(self, exc_type, exc_value, tb):
        if tb is None:
            self.pop()


def make_application(app):
    application = accost_application = WSGIApplication(app)
    middlewares = getattr(app, "middlewares", [])
    for middleware_path in middlewares:
        if isinstance(middleware_path, tuple):
            middleware_path, params = middleware_path[0], middleware_path[1:]
        else:
            params = ()
        try:
            module, cls = middleware_path.rsplit(".", 1)
            middleware = getattr(__import__(module, {}, {}, fromlist=[module]), cls)
            application = middleware(application, *params)
        except Exception, e:
            print >> sys.stderr, "warning: fail to load middleware: %s"%middleware_path
            print >> sys.stderr, e
    application.accost_application = accost_application
    return application


class Rules(list):
    def expose(self, string, **kw):
        def _inner(app):
            rule = Rule(string, **kw)
            rule.endpoint = app
            self.append(rule)
            return app
        return _inner

    def submount(self, string):
        rules = Rules()
        self.append(Submount(string, rules))
        return rules

    def subdomain(self, subdomain):
        rules = Rules()
        self.append(Subdomain(subdomain, rules))
        return rules
home = Rules()


class WSGIApplication(object):
    def __init__(self, app):
        self.accost_application = self
        self.app = app
        p = abspath(dirname(app.__file__))
        # automatic get url_mapping
        views_path = join(p, 'views')
        for module in get_modules([app.__name__, 'views'], views_path):
            __import__(module)
        self.url_mapping = Map(redirect_defaults=False)
        for Klass in get_subclasses(Handler):
            if Klass.rule is not None:
                if not isinstance(Klass.rule, (list, tuple)):
                    rules = [Klass.rule]
                else:
                    rules = Klass.rule
                for rule in rules:
                    rule.endpoint = Klass
                    self.url_mapping.add(rule)
        global home
        for rule in home:
            self.url_mapping.add(rule)
        self.url_mapping.update()
        self.db = getattr(app, 'db', {})
        self.debug = getattr(app, 'debug', True)
        self.session_store = getattr(app, 'session_store', None) or SessionStore()
        self.secret_key = getattr(app, 'secret_key',
                '\x80\x160m`6[\xff\xecK\x8c\xd8?h\x00Tsx_v')

    def __call__(self, environ, start_response):
#        start_response('200 OK', [('Content-type','text/plain')])
#        return ['Hello World!']
        adapter = self.url_mapping.bind_to_environ(environ)
        environ["accost.session_store"] = self.session_store
        environ["accost.secret_key"] = self.secret_key
        environ["accost.kwargs"] = {}
        global request
        with RequestContext(environ):
            # url mapping
            try:
                for store_name, store in self.db.items():
                    store.rollback()
                endpoint, kwargs = adapter.match()
                environ["accost.kwargs"] = kwargs
                # turn hander class path like 'www.views.main.index' to a real
                # handler class like index
                if isinstance(endpoint, basestring):
                    dot = endpoint.rindex('.')
                    module, cls = endpoint[:dot], endpoint[dot+1:]
                    endpoint = __import__(module, {}, {}, [cls])
                if type(endpoint) is type and issubclass(endpoint, Handler):
                    # build handler
                    handler = endpoint()
                    handler.errors = {}
                    for store_name, store in self.db.items():
                        setattr(handler, store_name, store)
                    # build request with session
                    mixins = getattr(handler, "mixins", ())
                    if mixins:
                        Request = type("Request", (AccostRequest,)+tuple(mixins), {})
                        handler.request = Request(environ)
                    else:
                        handler.request = request
                    response = handler(kwargs)
                    assert isinstance(response, BaseResponse)
                elif type(endpoint) is types.FunctionType:
                    response = endpoint()
                else:
                    return ClosingIterator(endpoint(environ, start_response), self.cleanup)
                request.finish(response)
            except HTTPException, e:
                logger.warn("%s %s"%(request.url, e))
                response = e
            except Exception, e:
                cookie_str = u"\n".join(u"%s=%s"%(k, v) for k, v in request.cookies.iteritems())
                logger.critical("%s cookie:\n%s\n%s"%(request.url, cookie_str.encode("utf-8"), format_exc()))
                if self.debug:
                    raise
                response = InternalServerError()
            # do response
            return ClosingIterator(response(environ, start_response), self.cleanup)

    def cleanup(self):
        for session in getattr(self.app, "sqlalchemy_sessions", []):
            session.remove()
        for store_name, store in self.db.items():
            store.rollback()


def get_subclasses(Klass):
    for sub_class in Klass.__subclasses__():
        yield sub_class
        for sub_sub_class in get_subclasses(sub_class):
            yield sub_sub_class
